<?php
/**
 * Created by zengxianfen on 2020/7/17 17:02.
 */

namespace App\Model\Service;


use App\Amqp\Builder\AmqpBuilder;
use App\Amqp\Producer\DelayProducer;
use App\Amqp\Producer\SMSSendProducer;
use App\Exception\ServiceException;
use App\Model\Repository\SMSRepository;
use Hyperf\Di\Annotation\Inject;
use Hyperf\Redis\Redis;
use Hyperf\Utils\ApplicationContext;

class SMSService extends BaseService
{
    /**
     * @Inject()
     * @var SMSRepository
     */
    protected $repository;

    /**
     * @Inject()
     * @var CompanyService
     */
    protected $companyService;

    /**
     * @Inject()
     * @var Redis
     */
    protected $redis;



    const SMS_CODE_MINUTER = 'SMS_CODE_MINUTER:';

    const SMS_CODE_DAY = 'SMS_CODE_DAY:';

    /**
     * 生成短信随机验证码
     * @author zengxianfen 2020/7/17
     */
    private static function genCode(int $length = 6)
    {
        $code = '';
        for ($i=0;$i<$length;$i++){
            $code .= mt_rand(0, 9);
        }
        return $code;
    }

    /**
     * 生成短信随机key
     * @author zengxianfen 2020/7/17
     */
    private static function genCodeKey(string $code = '')
    {
        $md5 = md5(time().$code);
        return $md5;
    }

    /**
     * 允许发送短信的业务场景
     * @author zengxianfen 2020/7/17
     * @return array
     */
    private static function getCodeScene()
    {
        return [
            'register',
            'resetPassword',
            'login'
        ];
    }

    /**
     * 生成并发送短信码
     * @author zengxianfen 2020/7/20
     * @param array $code
     * @return array
     * @throws ServiceException
     */
    public function getCode(array $code) {
        //验证场景
        $scene = $code['scene'];
        if (!in_array($scene, self::getCodeScene())){
            throw new ServiceException("获取短信失败，请确认业务场景");
        }
        //同一手机号，1分钟内只能发送一次短信，一天内最多只能发送5条短信
        $phone = $code['phone'];
        $ttl = $this->redis->ttl(self::SMS_CODE_MINUTER.$phone);
        if ($ttl > 0){
            throw new ServiceException("请在{$ttl}秒后重新获取验证码");
        }
        $smsCodeDayKey = self::SMS_CODE_DAY.date('Ymd');
        $dayCount = $this->redis->get($smsCodeDayKey.$phone)??0;
        if ($dayCount >= 5){
            throw new ServiceException("每个用户每天最多接收5条短信");
        }
        //获取随机值
        $sendCode = self::genCode();
        //获取验证随机key
        $codeKey = self::genCodeKey($sendCode);
        //设置redis值
        //1分钟内只能获取1条短信
        $this->redis->set(self::SMS_CODE_MINUTER.$phone, $scene, 60);
        //codeKey只允许校验1次，redis获取值后应立即删除
        $this->redis->set($codeKey, $scene, 600);
        //获取redis短信验证码时，需根据传入的codeKey获取场景后获取code
        $this->redis->set(self::SMS_CODE_MINUTER.$scene.$phone, $sendCode, 600);
        //每日发送数量++
        if (!$this->redis->exists($smsCodeDayKey.$phone)){
            $this->redis->set($smsCodeDayKey.$phone, 0 , 60*60*24);
        }
        $this->redis->incr($smsCodeDayKey.$phone);
        //入库
        $sms = $this->repository->create([
            'scene' => $scene,
            'phone' => $phone,
            'code' => $sendCode,
            'created_time' => date('Y-m-d H:i:s'),
        ]);
        //入队列发送短信
        if (!config('debug')){
            $message = new SMSSendProducer($sms);
            $builder = new AmqpBuilder();
            $builder->setDelayedQueue('sms-send', 60, 'sms-dlx-send', 'sms-dlx-send');
            $producer = ApplicationContext::getContainer()->get(DelayProducer::class);
            $producer->produce($message, $builder);
        }
        $rs = [
            'key' => $codeKey
        ];
        if (config('debug')){
            $rs['code'] = $sendCode;
        }
        return $rs;
    }


    /**
     * 校验手机验证码
     * @author zengxianfen 2020/7/20
     * @param $codeKey
     * @param $phone
     * @param $code
     * @throws ServiceException
     */
    public function checkCode(string $codeKey, string $phone, string $code) {
        //codeKey只允许校验1次，redis获取值后应立即删除
        $scene = $this->redis->get($codeKey);
        if (empty($scene)){
            throw new ServiceException("验证码错误");
        }
        $this->redis->del($codeKey);
        //获取验证码
        $existsCode = $this->redis->get(self::SMS_CODE_MINUTER.$scene.$phone);
        if (empty($scene)){
            throw new ServiceException("验证码错误");
        }
        if ($existsCode != $code) {
            throw new ServiceException("验证码错误");
        }
    }
}